﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using VisualEsse.DataEngine.Types;
using SvgNet.SvgGdi;
using VisualEsse;
using VisualEsse.Visualization;
using System.Drawing.Drawing2D;
using Faml;
using Faml.Compiler;
using Faml.Frontend;
using System.IO;
using System.Collections.Specialized;
using System.Xml;
using DAI.ClientToolkit;
using System.Globalization;

namespace VisualFaml2
{
    public partial class MainForm : Form
    {
        private TextBox tb = new TextBox();
        public MainForm()
        {
            InitializeComponent();

            tb.Multiline = true;
            tb.Dock = DockStyle.Fill;
            tb.Font = new Font("Consolas", 13);
          
            mainSplitter.Panel2.Controls.Add(tb);
        }

        public static void Main(string[] args)
        {
            Application.Run(new MainForm());
        }

        private XmlDocument PreparePerformDoc(string faml)
        {
            string doc = "<?xml version=\"1.0\" ?>" +
                "<perform xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"http://ogsadai.org.uk/namespaces/2005/10/types\">" +
                "<FamlActivity name=\"famlExpression\">" +
                "<output name=\"famlResult\" />" +
                "<faml><![CDATA[" + faml + "]]></faml></FamlActivity></perform>";
            XmlDocument perform = new XmlDocument();
            perform.LoadXml(doc);
            return perform;
        }

        private Array ReadArray(int[] shape, string[] values, Type type)
        {
            Array data = Array.CreateInstance(type, shape);

            int[] indices = new int[shape.Length];

            if (type == typeof(Double))
            {
                for (int i = 0; i < values.Length; i++)
                {
                    data.SetValue(double.Parse(values[i],CultureInfo.InvariantCulture), indices);
                    int j = 0;
                    while (true)
                    {
                        indices[j]++;
                        if (indices[j] >= shape[j])
                        {
                            indices[j] = 0;
                            if (j < data.Rank - 1)
                                j++;
                            else
                                break;
                        }
                        else
                            break;
                    }
                }
            }
            else if (type == typeof(int))
            {
                for (int i = 0; i < values.Length; i++)
                {
                    data.SetValue(int.Parse(values[i],CultureInfo.InvariantCulture), indices);
                    int j = 0;
                    while (true)
                    {
                        indices[j]++;
                        if (indices[j] >= shape[j])
                        {
                            indices[j] = 0;
                            if (j < data.Rank - 1)
                                j++;
                            else
                                break;
                        }
                        else
                            break;
                    }
                }
            }
            else throw new Exception("Unexpected type");

            return data;
        }

        private object EvalOGSADAI(string expression)
        {
            object resultObj = null;
            try
            {
                string url = ogsaDaiUrl.Text;

                DataServiceResource esse = new DataService(url).DataServiceResource("EsseCdmResource");
                XmlDocument perform = PreparePerformDoc(expression);


                DateTime now = DateTime.Now;
                XmlElement resp = esse.Perform(perform);
                TimeSpan delta = DateTime.Now - now;


                XmlNamespaceManager ns = new XmlNamespaceManager(new NameTable());
                ns.AddNamespace("t", Namespaces.Types);
                string result = ((XmlElement)resp.SelectSingleNode("//t:result[@name='famlResult']", ns)).InnerText;

                XmlDocument resDoc = new XmlDocument();
                resDoc.LoadXml(result);

                OrderedDictionary dict = new OrderedDictionary(3);

                XmlNamespaceManager nsman = new XmlNamespaceManager(resDoc.NameTable);
                nsman.AddNamespace("ns", "http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2");

                Dictionary<string, int> dims = new Dictionary<string, int>();
                XmlNodeList xmldims = resDoc.SelectNodes("/ns:netcdf/ns:dimension", nsman);
                foreach (XmlNode dim in xmldims)
                {
                    string name = dim.Attributes["name"].Value;
                    string length = dim.Attributes["length"].Value;
                    dims.Add(name, int.Parse(length));
                }

                XmlNodeList nodes = resDoc.SelectNodes("/ns:netcdf/ns:variable",nsman);
                Array data;
                foreach (XmlNode var in nodes)
                {
                    string name = var.Attributes["name"].Value;
                    string type = var.Attributes["type"].Value;

                    string[] sshape = var.Attributes["shape"].Value.Split(' ');
                    int[] shape = new int[sshape.Length];
                    for (int i = 0; i < shape.Length; i++)
                        shape[i] = dims[sshape[i]];

                    data = ReadArray(shape, var.InnerText.Split(' '), GetType(type));
                    dict.Add(name, data);
                }
                resultObj = dict;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + "\n" + ex.StackTrace);
            }

            return resultObj;
        }

        private Type GetType(string type)
        {
            switch (type)
            {
                case "double":
                    return typeof(double);
                case "int":
                    return typeof(int);
                default:
                    throw new Exception("Unexpected type " + type);
            }
        }

        private void OnShowData(object sender, EventArgs e)
        {
            object result = null;
            try
            {
                string baseDefs = "";

                if (File.Exists("BaseDefs.faml"))
                    baseDefs = File.ReadAllText("BaseDefs.faml") + "\n";

                if (localToolStripMenuItem.Checked)
                {
                    RuntimeEnvironment fre = RuntimeEnvironment.GetDefaultRuntimeEnvironment();

                    FamlProgram fp = FamlProgram.Compile(fre, baseDefs + tb.Text);
                    result = fp.Evaluate();
                }
                else
                {
                    result = EvalOGSADAI(/*baseDefs + */tb.Text);
                }
            }
            catch (Exception exc)
            {
                MessageBox.Show(exc.Message + "\n" + exc.StackTrace, "Error in Faml program");
            }

            OrderedDictionary d = result as OrderedDictionary;
            if (d == null)
                MessageBox.Show("Result is not a composite object", "Unable to plot");
            else
                PlotDictionary(d);

          /*  GeoRect region = new GeoRect(-180,180,-90,90);
            GeoUniformGrid2d grid = new GeoUniformGrid2d(new GeoPoint(1, 1), region);
            VisualEsse.DataEngine.Types.ScalarArray2d data = new VisualEsse.DataEngine.Types.ScalarArray2d(361, 181);
            for (int i = 0; i < 361; i++)
                for (int j = 0; j < 181; j++)
                    data[i, j] = 5 * Math.Sin(i / 10) * Math.Cos(j / 40.0);
            GeoUniformScalarField2d field = new GeoUniformScalarField2d(grid, data);
            Size imageSize = new Size(800, 400);
            Bitmap bm = new Bitmap(imageSize.Width,imageSize.Height);*/

        }

        private T GetField<T>(OrderedDictionary result, string field, out int index) where T : class
        {
            index = -1;
            if (!result.Contains(field))
            {
                MessageBox.Show("No " + field + " field in result object", "Unable to plot!");
                return null;
            }

            T fieldValue = result[field] as T;
            if (fieldValue == null)
            {
                MessageBox.Show("Value should be of type" + typeof(T).Name, "Unable to plot!");
                return null;
            }

            for (int i = 0; i < result.Count; i++)
            {
                if (result[i] == fieldValue)
                {
                    index = i; break;
                }
            }            
            
            return fieldValue;
        }

        private void PlotDictionary(OrderedDictionary data)
        {
            mainSplitter.Panel1.Controls.Clear();
            if (data.Contains("Values") && data.Contains("Lats") && data.Contains("Lons"))
                PlotGeoField(data);
            else if (data.Contains("Values") && data.Contains("Grid"))
                PlotGraph(data);
        }

        private void PlotGeoField(OrderedDictionary data)
        {
            int iv, ilats, ilons;
            double[,] values = GetField<double[,]>(data, "Values", out iv);
            double[] lats = GetField<double[]>(data, "Lats", out ilats);
            double[] lons = GetField<double[]>(data, "Lons", out ilons);
            if (values == null || lats == null || lons == null)
                return;

            if (ilats < ilons)
            {
                ilats = 0; ilons = 1;
            }
            else
            {
                ilats = 1; ilons = 0;
            }

            if (values.GetLength(ilons) != lons.Length)
            {
                MessageBox.Show("Longitude dimensions does not match!");
                return;
            }
            if (values.GetLength(ilats) != lats.Length)
            {
                MessageBox.Show("Latitude dimensions does not match!");
                return;
            }
            PlotGeoField(values, lats, ilats, lons, ilons);
        }

        private void PlotGraph(OrderedDictionary data)
        {
            int iv, ig;
            double[] values = GetField<double[]>(data, "Values", out iv);
            double[] grid = GetField<double[]>(data, "Grid", out ig);
            if (values == null || grid == null)
                return;
            if (values.Length != grid.Length)
            {
                MessageBox.Show("Values and grid dimensions does not match!");
                return;
            }
            Plotter.PlotterControl pc = new Plotter.PlotterControl();
            pc.Grid = new VisualEsse.DataEngine.Types.NonUniformGrid1d(grid);
            pc.AddGraph(values, Color.Blue, "1");
            pc.Dock = DockStyle.Fill;
            mainSplitter.Panel1.Controls.Add(pc);
        }


        private void PlotGeoField(double[,] values, double[] lats, int ilats, double[] lons, int ilons)
        {
            Array.Sort<double>(lons);
            Array.Sort<double>(lats);

            GeoRect region = new GeoRect((float)lons[0], (float)lons[lons.Length - 1],
                                         (float)lats[0], (float)lats[lats.Length - 1]);
            GeoUniformGrid2d grid = new GeoUniformGrid2d(new GeoPoint(
                region.HeightLatitude / (lats.Length - 1),
                region.WidthLongitude / (lons.Length - 1)), region); // HACK: NonUniform -> uniform
            VisualEsse.DataEngine.Types.ScalarArray2d data = new VisualEsse.DataEngine.Types.ScalarArray2d(lons.Length, lats.Length);
            if(ilats < ilons)
                for (int i = 0; i < lons.Length; i++)
                    for (int j = 0; j < lats.Length; j++)
                        data[i, j] = values[j, i];
            else
                for (int i = 0; i < lons.Length; i++)
                    for (int j = 0; j < lats.Length; j++)
                        data[i, j] = values[i, j];
            GeoUniformScalarField2d field = new GeoUniformScalarField2d(grid, data);
            Size imageSize = new Size(500, 500);
            Bitmap bm = new Bitmap(imageSize.Width, imageSize.Height);

            Isoline2dPlotter plotter = new Isoline2dPlotter();
            plotter.LevelsSelection = LevelsSelection.BoundLevels;
            plotter.LevelOrigin = 0.0;
            plotter.LevelOriginStep = 0.5;
            // How many Labels per Pixels?
            plotter.WayBeforeText = (region.HeightLatitude + region.WidthLongitude) / ((1.0 / 150.0) * (imageSize.Width + imageSize.Height));
            using (Graphics g = Graphics.FromImage(bm))
            {
                IGraphics ig = new GdiGraphics(g);
                using (GDICompositeGeometricObject geometry = new GDICompositeGeometricObject(ig))
                {
                    plotter.Work(field, geometry);

                    Matrix transform = new Matrix(
                        (float)((double)imageSize.Width / region.WidthLongitude), 0.0f,
                        0.0f, (float)(-(double)imageSize.Height / region.HeightLatitude),
                        (float)(-region.LeftLongitude * imageSize.Width / region.WidthLongitude),
                        (float)(region.TopLatitude * imageSize.Height / region.HeightLatitude));

                    geometry.Draw(transform);
                }
            }

            PictureBox vb = new PictureBox();
            vb.Dock = DockStyle.Fill;
            vb.Image = bm;
            mainSplitter.Panel1.Controls.Add(vb);
        }

        private void localToolStripMenuItem_Click(object sender, EventArgs e)
        {
            localToolStripMenuItem.Checked = !localToolStripMenuItem.Checked;
            oGSADAIToolStripMenuItem.Checked = !localToolStripMenuItem.Checked;
        }

        private void oGSADAIToolStripMenuItem_Click(object sender, EventArgs e)
        {
            oGSADAIToolStripMenuItem.Checked = !oGSADAIToolStripMenuItem.Checked;
            localToolStripMenuItem.Checked = !oGSADAIToolStripMenuItem.Checked;
        }

        private void ogsaDaiAddressToolStripMenuItem_DropDownClosed(object sender, EventArgs e)
        {
            if (VisualFaml2.Properties.Settings.Default.OGSADAIUrl != ogsaDaiUrl.Text)
            {
                VisualFaml2.Properties.Settings.Default.OGSADAIUrl = ogsaDaiUrl.Text;
                VisualFaml2.Properties.Settings.Default.Save();
            }
        }
    }
}
